package de.jmda.gen.java.impl;

import static de.jmda.core.util.StringUtil.endsWith;
import static de.jmda.core.util.StringUtil.rTrimChars;

import java.util.ArrayList;
import java.util.List;

import javax.validation.constraints.NotNull;

import de.jmda.gen.Generator;
import de.jmda.gen.GeneratorException;
import de.jmda.gen.impl.AbstractCompoundGenerator;
import de.jmda.gen.java.DeclaredMethodGenerator;
import de.jmda.gen.java.MethodBodyGenerator;
import de.jmda.gen.java.MethodHeaderGenerator;
import de.jmda.gen.java.MethodModifiersGenerator;
import de.jmda.gen.java.MethodNameGenerator;
import de.jmda.gen.java.MethodParametersGenerator;
import de.jmda.gen.java.ParameterGenerator;
import de.jmda.gen.java.ThrowsClauseGenerator;
import de.jmda.gen.java.TypeNameGenerator;

/**
 * A declared method is built from multiple hierarchical generators.
 *
 * @author rwegner
 */
public abstract class AbstractDeclaredMethodGenerator
		extends AbstractCompoundGenerator
		implements DeclaredMethodGenerator
{
	private @NotNull MethodHeaderGenerator methodHeaderGenerator;
	private MethodBodyGenerator methodBodyGenerator;

	public AbstractDeclaredMethodGenerator()
	{
		this(null, null, null, null, null, null);
	}
	
	/**
	 * Sets component separator for component generation process to <code>{@link
	 * System#lineSeparator()}</code>.
	 *
	 * @param modifiersGenerator
	 * @param typeNameGenerator
	 * @param methodNameGenerator
	 * @param methodParametersGenerator
	 * @param methodBodyGenerator
	 */
	public AbstractDeclaredMethodGenerator(
			MethodModifiersGenerator modifiersGenerator,
			TypeNameGenerator typeNameGenerator,
			MethodNameGenerator methodNameGenerator,
			MethodParametersGenerator methodParametersGenerator,
			ThrowsClauseGenerator throwsClauseGenerator,
			MethodBodyGenerator methodBodyGenerator)
	{
		super();
		
		methodHeaderGenerator =
				new DefaultMethodHeaderGenerator(
						modifiersGenerator,
						typeNameGenerator,
						new DefaultMethodDetailsGenerator(
								new DefaultMethodSignatureGenerator(
										methodNameGenerator, methodParametersGenerator),
								throwsClauseGenerator));
		
		setMethodBodyGenerator(methodBodyGenerator);

		setComponentSeparator(System.lineSeparator());
	}

	/**
	 * Returns collection of <code>Generator</code>s with the return values of the
	 * following methods in the following order:
	 * <ul>
	 *   <li>{@link #getMethodHeaderGenerator()} and</li>
	 *   <li>{@link #getMethodBodyGenerator()}</li>
	 * </ul>
	 *
	 * @see de.jmda.gen.CompoundGenerator#getGeneratorComponents()
	 */
	@Override
	public List<Generator> getGeneratorComponents()
	{
		List<Generator> result = new ArrayList<Generator>();
		
		MethodBodyGenerator lclMethodBodyGenerator = getMethodBodyGenerator();
		
		result.add(getMethodHeaderGenerator());
		result.add(lclMethodBodyGenerator);
		
		if (lclMethodBodyGenerator == null)
		{
			result.add(dg(";"));
		}

		return result;
	}

	/**
	 * Calls <code>super.generate()</code> to produce the generation result. If
	 * necessary ";" will be appended afterwards.
	 *
	 * @see de.jmda.gen.impl.AbstractCompoundGenerator#generate()
	 */
	@Override
  public StringBuffer generate() throws GeneratorException
  {
		StringBuffer result = super.generate();

		if (endsWith(result, System.lineSeparator() + ";"))
		{
			result = rTrimChars(result, System.lineSeparator() + ";").append(";");
		}

		return result;
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#getMethodHeaderGenerator()
	 */
	@Override
	@NotNull
  public MethodHeaderGenerator getMethodHeaderGenerator()
  {
		return notNull(methodHeaderGenerator);
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#getModifiersGenerator()
	 */
	@Override
  public MethodModifiersGenerator getModifiersGenerator()
  {
		return getMethodHeaderGenerator().getModifiersGenerator();
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#getTypeNameGenerator()
	 */
	@Override
	@NotNull
  public TypeNameGenerator getTypeNameGenerator()
  {
		return getMethodHeaderGenerator().getTypeNameGenerator();
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#getMethodNameGenerator()
	 */
	@Override
	@NotNull
  public MethodNameGenerator getMethodNameGenerator()
  {
		return
				getMethodHeaderGenerator().getMethodDetailsGenerator()
				                          .getMethodSignatureGenerator()
				                          .getMethodNameGenerator();
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#getMethodParametersGenerator()
	 */
	@Override
	@NotNull
  public MethodParametersGenerator getMethodParametersGenerator()
  {
		return
				getMethodHeaderGenerator().getMethodDetailsGenerator()
				                          .getMethodSignatureGenerator()
				                          .getMethodParametersGenerator();
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#getThrowsClauseGenerator()
	 */
	@Override
  public ThrowsClauseGenerator getThrowsClauseGenerator()
  {
		return
				getMethodHeaderGenerator().getMethodDetailsGenerator()
				                          .getThrowsClauseGenerator();
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#getMethodBodyGenerator()
	 */
	@Override
  public MethodBodyGenerator getMethodBodyGenerator()
  {
		return methodBodyGenerator;
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#setModifiersGenerator(de.jmda.gen.java.MethodModifiersGenerator)
	 */
	@Override
	public void setModifiersGenerator(MethodModifiersGenerator generator)
  {
		getMethodHeaderGenerator().setModifiersGenerator(generator);
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#setTypeNameGenerator(de.jmda.gen.java.TypeNameGenerator)
	 */
	@Override
	public void setTypeNameGenerator(@NotNull TypeNameGenerator generator)
  {
		getMethodHeaderGenerator().setTypeNameGenerator(generator);
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#setMethodNameGenerator(de.jmda.gen.java.MethodNameGenerator)
	 */
	@Override
	public void setMethodNameGenerator(@NotNull MethodNameGenerator generator)
  {
		getMethodHeaderGenerator().getMethodDetailsGenerator()
		                          .getMethodSignatureGenerator()
		                          .setMethodNameGenerator(generator);
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#setMethodParametersGenerator(de.jmda.gen.java.MethodParametersGenerator)
	 */
	@Override
	public void setMethodParametersGenerator(
			@NotNull MethodParametersGenerator generator)
  {
		getMethodHeaderGenerator().getMethodDetailsGenerator()
		                          .getMethodSignatureGenerator()
		                          .setMethodParametersGenerator(generator);
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#setThrowsClauseGenerator(de.jmda.gen.java.ThrowsClauseGenerator)
	 */
	@Override
	public void setThrowsClauseGenerator(ThrowsClauseGenerator generator)
  {
		getMethodHeaderGenerator().getMethodDetailsGenerator()
		                          .setThrowsClauseGenerator(generator);
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#setMethodHeaderGenerator(de.jmda.gen.java.MethodHeaderGenerator)
	 */
	@Override
  public void setMethodHeaderGenerator(@NotNull MethodHeaderGenerator generator)
  {
		methodHeaderGenerator = notNull(generator);
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#setMethodBodyGenerator(de.jmda.gen.java.MethodBodyGenerator)
	 */
	@Override
	public void setMethodBodyGenerator(MethodBodyGenerator generator)
  {
		methodBodyGenerator = generator;
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#add(de.jmda.gen.java.ParameterGenerator)
	 */
	@Override
  public void add(ParameterGenerator generator)
  {
		getMethodParametersGenerator().getParametersGenerator()
		                              .getParameterGenerators().add(generator);
  }

	/**
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#setMethodBody(java.lang.String)
	 */
	@Override
  public void setMethodBody(String methodBody)
  {
		if (methodBody == null)
		{
			setMethodBodyGenerator(null);
		}
		else
		{
			demandMethodBodyGenerator().setMethodBody(methodBody);
		}
  }

	/**
	 * @NotNull
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#demandModifiersGenerator()
	 */
	@Override
	public MethodModifiersGenerator demandModifiersGenerator()
  {
		return getMethodHeaderGenerator().demandModifiersGenerator();
  }

	/**
	 * @NotNull
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#demandThrowsClauseGenerator()
	 */
	@Override
	public ThrowsClauseGenerator demandThrowsClauseGenerator()
  {
		return
				getMethodHeaderGenerator().getMethodDetailsGenerator()
				                          .demandThrowsClauseGenerator();
  }

	/**
	 * @NotNull
	 * @see de.jmda.gen.java.DeclaredMethodGenerator#demandMethodBodyGenerator()
	 */
	@Override
	public MethodBodyGenerator demandMethodBodyGenerator()
  {
		methodBodyGenerator = notNull(methodBodyGenerator);
	  return methodBodyGenerator;
  }

	private MethodHeaderGenerator notNull(MethodHeaderGenerator generator)
  {
		if (generator == null)
		{
			generator = new DefaultMethodHeaderGenerator();
		}

		return generator;
  }

	protected static MethodBodyGenerator notNull(MethodBodyGenerator generator)
  {
		if (generator == null)
		{
			generator = new DefaultMethodBodyGenerator();
		}

		return generator;
  }
}